#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "route_sync.h"

#include "main.h"
#include "route.h"

/**
 * @brief Execute shell command and capture output as dynamically allocated string
 * This function provides a safe wrapper around popen to execute system commands
 * and capture their standard output for further processing in the application
 * @param command: Shell command string to be executed in system environment
 * @return char* Returns dynamically allocated string containing command output,
 *         or NULL if command execution fails or memory allocation error occurs
 */
char *execc(const char *command) {
    /* Pointer to store the accumulated command output string */
    char *result = NULL;
    
    /* Variable to track the total size of accumulated output */
    size_t size = 0;
    
    /* Execute command and create pipe to read output stream */
    FILE *pipe = popen(command, "r");
    
    /* Verify command execution was successful before proceeding */
    if (!pipe) return NULL;

    /* Buffer for reading command output in chunks */
    char buffer[128];
    
    /* Read command output line by line until end of stream */
    while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
        /* Calculate length of current buffer content */
        size_t len = strlen(buffer);
        
        /* Reallocate result buffer to accommodate new data with null terminator */
        char *temp = realloc(result, size + len + 1);
        
        /* Handle memory allocation failure with proper cleanup */
        if (!temp) {
            free(result);
            return NULL;
        }
        
        /* Update result pointer to new memory location */
        result = temp;
        
        /* Append current buffer content to the result string */
        strcpy(result + size, buffer);
        
        /* Update total size counter for next allocation */
        size += len;
    }

    /* Close the command execution pipe and free system resources */
    pclose(pipe);
    
    /* Return the accumulated command output string */
    return result;
}

/**
 * @brief Parse IP address and subnet mask from CIDR notation input string
 * This function processes network address input in CIDR format (e.g., "192.168.1.0/24")
 * and separates it into individual IP address and subnet mask components
 * @param input: Input string containing IP address in CIDR notation format
 * @param ip: Output buffer to store extracted IP address component
 * @param mask: Output buffer to store calculated subnet mask
 */
void parse_ip_and_mask(const char *input, char *ip, char *mask) {
    // Initialize the mask to default value for cases without CIDR notation
    strcpy(mask, "255.255.255.0"); 

    // Copy the input to a local buffer for safe string manipulation
    char buffer[MAX_STR_LEN];
    
    // Copy input string to local buffer with bounds checking
    strncpy(buffer, input, MAX_STR_LEN-1);
    
    // Ensure null termination to prevent buffer overflow issues
    buffer[MAX_STR_LEN-1] = '\0'; 

    // Find the position of the slash character indicating CIDR notation
    char *slash = strchr(buffer, '/');
    
    // Process CIDR notation if slash character is present in input
    if (slash != NULL) {
        // Split the input string into IP and prefix length at slash position
        *slash = '\0';
        
        // Convert prefix length string to integer value for mask calculation
        int prefix = atoi(slash + 1);

        // Convert prefix length to binary subnet mask representation
        unsigned int mask_bin = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF;
        
        // Format binary mask into dotted-decimal string representation
        snprintf(mask, MAX_STR_LEN, "%u.%u.%u.%u",
                 (mask_bin >> 24) & 0xFF,
                 (mask_bin >> 16) & 0xFF,
                 (mask_bin >> 8) & 0xFF,
                 mask_bin & 0xFF);
    }

    // Copy the IP part to the output parameter with bounds checking
    strncpy(ip, buffer, MAX_STR_LEN-1);
    
    // Ensure null termination of output IP string
    ip[MAX_STR_LEN-1] = '\0'; 
}

/**
 * @brief Add route entry to specific device in the routing table structure
 * This function manages the routing table by adding new route entries to existing
 * devices or creating new device entries when necessary with proper error checking
 * @param rt: Pointer to the main routing table structure to be updated
 * @param device: Network interface device name for the route entry (e.g., "ppp0")
 * @param destination: Destination network address for the route entry
 * @param mask: Subnet mask associated with the destination network
 */
void add_route_to_device(RouteTable *rt, const char *device, const char *destination, const char *mask) {
    // Find the device in the route table by iterating through existing devices
    Device *dev = NULL;
    
    // Linear search through device array to find matching device name
    for (int i = 0; i < rt->device_count; i++) {
        if (strcmp(rt->devices[i].device, device) == 0) {
            dev = &rt->devices[i];
            break;
        }
    }
    
    // If device is not found, add a new device entry to the routing table
    if (dev == NULL) {
        // Check if maximum device capacity has been reached
        if (rt->device_count >= MAX_DEVICES) {
            fprintf(stderr, "Error: Maximum number of devices reached.\n");
            return;
        }
        
        // Initialize new device entry at the next available position
        dev = &rt->devices[rt->device_count++];
        
        // Copy device name to the new device structure
        strcpy(dev->device, device);
        
        // Initialize route count for the new device
        dev->route_count = 0;
    }

    // Add the route to the device's route array with capacity checking
    if (dev->route_count >= MAX_ROUTES) {
        fprintf(stderr, "Error: Maximum number of routes for device %s reached.\n", device);
        return;
    }
    
    // Get pointer to the next available route slot in the device's route array
    Route *route = &dev->routes[dev->route_count++];
    
    // Copy destination address and mask to the route structure
    strcpy(route->destination, destination);
    strcpy(route->mask, mask);
}

/**
 * @brief Parse single line from ip route command output and extract routing information
 * This function processes individual lines from the system routing table output,
 * extracting destination networks, subnet masks, and associated network interfaces
 * @param rt: Pointer to routing table structure to be populated with parsed data
 * @param line: Single line string from ip route command output to be parsed
 */
void parse_route_line(RouteTable *rt, const char *line) {
    // Initialize buffers for extracted route components with default values
    char destination[MAX_STR_LEN] = {0};
    char mask[MAX_STR_LEN] = "255.255.255.0"; // Default mask if not specified
    char device[MAX_STR_LEN] = {0};

    // Check if this line contains default route configuration
    if (strstr(line, "default") == line) {
        // Extract default route device using sscanf with pattern matching
        sscanf(line, "default %*s dev %s", device);
        
        // Store default device in routing table structure
        strcpy(rt->default_device, device);
    } else {
        // Extract regular route destination and device from non-default routes
        sscanf(line, "%s dev %s", destination, device);
    }

    // Skip further processing for default route lines to avoid duplication
    if (strncmp(line, "default", 7) == 0) {
        return;
    }

    // Validate that the extracted device name represents a supported interface type
    if (!is_dev_type_valid(device)){
        return;
    }

    // Extract subnet mask from CIDR notation in destination field if present
    char *slash = strchr(destination, '/');
    if (slash != NULL) {
        // Split destination string at slash position for separate processing
        *slash = '\0';  // Split destination and mask
        
        // Convert prefix length from string to integer value
        int prefix = atoi(slash + 1);
        
        // Calculate binary subnet mask from prefix length
        unsigned int mask_bin = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF;
        
        // Convert binary mask to dotted-decimal string format
        snprintf(mask, MAX_STR_LEN, "%u.%u.%u.%u",
                 (mask_bin >> 24) & 0xFF,
                 (mask_bin >> 16) & 0xFF,
                 (mask_bin >> 8) & 0xFF,
                 mask_bin & 0xFF);
    }

    // Add the parsed route information to the routing table structure
    add_route_to_device(rt, device, destination, mask);
}

/**
 * @brief Parse complete ip route command output and build routing table structure
 * This function processes the multi-line output from system routing commands,
 * breaking it into individual lines and delegating parsing to line-level function
 * @param table: Pointer to routing table structure to be populated with parsed data
 * @param output: Multi-line string containing complete ip route command output
 */
void parse_routes(RouteTable *table, const char *output) {
    // Create a modifiable copy of the output string for tokenization
    char output_copy[MAX_STR_LEN * 10];
    
    // Copy output string to local buffer with bounds checking
    strncpy(output_copy, output, sizeof(output_copy) - 1);
    
    // Ensure null termination of the local copy for safe string operations
    output_copy[sizeof(output_copy) - 1] = '\0'; 

    // Tokenize the output string by newline characters to process each route line
    char *route_output = strtok((char *)output_copy, "\n");
    
    // Iterate through all route lines in the command output
    while (route_output != NULL) {
        // Parse individual route line and update routing table
        parse_route_line(table, route_output);
        
        // Get next route line for processing
        route_output = strtok(NULL, "\n");
    }
}

/**
 * @brief Display complete routing table contents in readable format
 * This debug function prints the entire routing table structure including
 * default routes and all device-specific routing entries for verification
 * @param table: Pointer to routing table structure to be displayed
 */
void print_routes(const RouteTable *table) {
    // Print the default route device information
    printf("Default Device: %s\n", table->default_device);
    
    // Iterate through all devices in the routing table
    for (int i = 0; i < table->device_count; i++) {
        // Print device name header
        printf("Device: %s\n", table->devices[i].device);
        
        // Iterate through all routes associated with current device
        for (int j = 0; j < table->devices[i].route_count; j++) {
            // Print individual route destination and mask information
            printf("  Route IP: %s, Mask: %s\n",
                   table->devices[i].routes[j].destination,
                   table->devices[i].routes[j].mask);
        }
    }
}

/**
 * @brief Display formatted routing table for specific network device
 * This function provides a structured table view of routing information
 * for a particular network interface with fixed column formatting
 * @param rt: Pointer to routing table structure containing route data
 * @param dev: Specific device name to filter and display routes for
 */
void print_route_table(RouteTable *rt, const char *dev) {
    // Print table header with fixed column widths for alignment
    printf("%-10s %-5s %-14s %-18s %-15s\n", "dev_name", "#", "isDefaultRoute", "route_ip", "netmask");
    
    // Iterate through all devices in the routing table
    for (int i = 0; i < rt->device_count; i++) {
        Device *device = &rt->devices[i];

        // Only print routes for the specified target device
        if (strcmp(device->device, dev) == 0) {
            // Determine if current device is configured as default route
            int isDefaultRoute = strcmp(device->device, rt->default_device) == 0 ? 1 : 0;

            // Iterate through all routes associated with the target device
            for (int j = 0; j < device->route_count; j++) {
                Route *route = &device->routes[j];
                
                // Print formatted route information with fixed column widths
                printf("%-10s %-5d %-14d %-18s %-15s\n", device->device, j + 1, isDefaultRoute, route->destination, route->mask);
            }
        }
    }
}

/**
 * @brief Synchronize system routing table with application routing table structure
 * This main synchronization function executes system routing commands, parses
 * the output, and updates the application's routing table representation
 * @param table: Pointer to routing table structure to be synchronized and updated
 * @return int Returns TRUE if synchronization successful, FALSE if any error occurs
 */
int wwan_route_sync(RouteTable * table)
{
    // Execute system command to retrieve current routing table information
    char *output = execc("ip route");
    
    // Verify command execution was successful and output was captured
    if (!output) {
        fprintf(stderr, "Failed to get route information\n");
        return FALSE;
    }

    // Parse the command output to populate the routing table structure
    parse_routes(table, output);

    // Debug option: print the parsed routing table for verification
    //print_routes(table);

    // Save the updated routing table to persistent configuration storage
    if (save_route_table_to_file(table)) {
        //printf("Routes saved successfully.\n");
    } else {
        printf("Failed to save routes to config file.\n");
    }

    // Free the dynamically allocated command output string
    free(output);    
    return TRUE;
}